package core;

import java.util.ArrayList;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

import org.json.JSONException;
import org.json.JSONObject;

public class Process {
	Graph graph;
	UUID id;
	public String name;
	String guard;
	
	ArrayList<Entity> entityIn;
	ArrayList<Entity> entityOut;
	ArrayList<String> evalList;
	
	ConcurrentHashMap<UUID, Node> implTable = new ConcurrentHashMap<UUID, Node>();
	boolean Accelerated = false;
	
	//HashMap<UUID, String> implStatus = new HashMap<UUID, String>();
	
	public Process(Graph g, String name, String guard){
		id = UUID.randomUUID();
		graph = g;
		graph.addProc(this);
		this.name = name;
		this.guard = guard;
		entityIn = new ArrayList<Entity>();
		entityOut = new ArrayList<Entity>();
		evalList = new ArrayList<String>();
	}
	
	public Process(Graph g, String name, String guard, ArrayList<Entity> eIn, ArrayList<Entity> eOut, ArrayList<String> eval){
		graph = g;
		id = UUID.randomUUID();
		this.name = name;
		this.guard = guard;
		entityIn = eIn;
		entityOut = eOut;
		evalList = eval;
	}
	
	public void addEntityIn(Entity e){
		this.entityIn.add(e);
		e.addProcOut(this);
	}
	
	public void addEntityOut(Entity e){
		this.entityOut.add(e);
		e.addProcIn(this);
	}
	
	public void addEvalItem(String s){
		this.evalList.add(s);
	}
	
	// Each Process can be implement may times :)
	public void implTo(Node node){
		UUID implId = UUID.randomUUID();
		implTable.put(implId, node);
		if(!node.loadProc(this.toJSONObject(implId))){
			
			log(this.name.toString(), implId.toString(), "loadProc()", false);
			
			JSONObject o = this.toJSONObject(implId);
			System.out.println("\t"+o.toString());
		}
		if(!node.runProc(implId.toString())){
			
			log(this.name.toString(), implId.toString(), "runProc()", false); // Make this remote instance run
			
		}
	}
	
	// Wake up all the remote implement instance
	public void recallAll(){
		for(UUID id : implTable.keySet()){
			if(!implTable.get(id).runProc(id.toString()))
				log(this.name.toString(), id.toString(), "recall()", false);
		}
	}
	
	// Make the remote implement process evaluate a bit faster
	public void accelerate(){
		for(UUID id : implTable.keySet()){
			if(!implTable.get(id).setProcPriority(id.toString(), 7))
				log(this.name.toString(), id.toString(), "accelerate()", false);
		}
		Accelerated = true;
	}
	
	public void speedRestore(){
		for(UUID id : implTable.keySet()){
			if(!implTable.get(id).setProcPriority(id.toString(), 5)) 
				log(this.name.toString(), id.toString(), "speedRestore()", false);
		}
		Accelerated = false;
	}
	
	// unload a remote instance
	public Node removeAImpl(){
		UUID thisId = null;
		for(UUID id : implTable.keySet()){
			thisId = id;
		}
		Node node = implTable.get(thisId);
		try{
			node.unloadProc(thisId.toString());
			implTable.remove(thisId);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return node;
	}
	
	public boolean hasSameEntityIn(Process p) {
		for(Entity e:p.entityIn){
			if(this.entityIn.contains(e)) return true;
		}
		return false;
	}
	
	// unload all remote instance
	public void unload(){
		for(UUID id : implTable.keySet()){
			implTable.get(id).unloadProc(id.toString());
			implTable.remove(id);
		}
	}
	
	// for remote implement
	public JSONObject toJSONObject(UUID id){
		JSONObject entity = new JSONObject();
		
		ArrayList<String> entityInID = new ArrayList<String>();
		ArrayList<String> entityOutID = new ArrayList<String>();
		
		for(Entity e : this.entityIn){
			entityInID.add(e.id.toString());
		}
		
		for(Entity e : this.entityOut){
			entityOutID.add(e.id.toString());
		}
		
		try {
			entity.put("id", id.toString());
			entity.put("name", name);
			entity.put("graph_id", graph.id);
			entity.put("guard", guard);
			entity.put("entityIn", entityInID);
			entity.put("entityOut", entityOutID);
			entity.put("eval", evalList);
		} catch (JSONException e) {
			e.printStackTrace();
		}
		return entity;
	}
	
	static void log(String id, String implId, String func, boolean b){
		System.out.println("Proc: "+id+" [Impl: "+implId+"] "+func+" "+b);
	}
}